/*
 * Copyright 2008-2013, David George, Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.magneato.utils.parsers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents a node in an XML tree. The HashMap is used to look up children. If
 * the children are part of a repeating group they are stored in an ArrayList.
 * The implementation is transparent to the end user.
 * 
 * @author david
 */
public class HashList implements Collection<HashList> {
	StringBuilder value;
	String name;
	// index to named nodes
	HashMap<String, ArrayList<HashList>> index;
	// list of all child nodes
	ArrayList<HashList> children;
	HashMap<String, String> attributes;

	private static final Logger _logger = LoggerFactory
			.getLogger(HashList.class);

	HashList(String name) {
		this.name = name;
		value = new StringBuilder();
		index = null;
	}

	public String getName() {
		return name;
	}

	public String getValue() {
		return value.toString();
	}

	/**
	 * Return value as integer, returns zero if the value is not a valid int,
	 * this may not be what you want!
	 * 
	 * @return
	 */
	public int getIntValue() {
		try {
			return Integer.parseInt(value.toString());
		} catch (NumberFormatException e) {
			_logger.error("Int Value" + name + " " + e.getLocalizedMessage());
		}
		return 0;
	}

	/**
	 * Return value as float, returns zero if the value is not a valid float,
	 * this may not be what you want!
	 * 
	 * @return
	 */
	public float getFloatValue() {
		try {
			return Float.parseFloat(value.toString());
		} catch (NumberFormatException e) {
			_logger.error("Float value " + name + " " + e.getLocalizedMessage());
		}
		return 0;
	}

	void setValue(StringBuilder value) {
		this.value.append(value);
	}

	void setValue(String value) {
		this.value.append(value);
	}

	/**
	 * Attributes are lazily initialized as we don't always have them. All names
	 * are lower case.
	 * 
	 * @param name
	 * @param value
	 */
	void addAttribute(String name, String value) {
		if (attributes == null) {
			attributes = new HashMap<String, String>(1);
		}
		attributes.put(name.toLowerCase(), value);
	}

	public String getAttribute(String name) {
		if (attributes != null) {
			return attributes.get(name.toLowerCase());
		}
		return null;
	}

	/**
	 * Add a named child node. If the named child already exists we are a
	 * repeating group, initialize the ArrayList and add the current value and
	 * the new node into this list. This lets us iterate over repeating values.
	 * 
	 * @param name
	 */
	HashList addNode(String name) {
		HashList currentNode;

		// if index is null we've never added a node before - do setup
		if (index == null) {
			ArrayList<HashList> elements = new ArrayList<HashList>();
			children = new ArrayList<HashList>();

			index = new HashMap<String, ArrayList<HashList>>();
			currentNode = new HashList(name);
			elements.add(currentNode);
			index.put(name, elements);
			add(currentNode);
		} else {
			if (index.containsKey(name)) {
				// already an element with this name, add another
				ArrayList<HashList> elements = index.get(name);
				// we need to create another node
				currentNode = new HashList(name);
				elements.add(currentNode);
				add(currentNode);
			} else {
				ArrayList<HashList> elements = new ArrayList<HashList>();
				currentNode = new HashList(name);
				elements.add(currentNode);
				index.put(name, elements);
				add(currentNode);
			}
		}

		return currentNode;
	}

	// shorthand for Velocity
	public HashList get(String name) {
		return getNode(name);
	}
	
	public boolean has(String value) {
		for (HashList child : children) {
			if (child.getValue().equals(value)) {
				return true;
			}
		}
		
		return false;
	}

	/**
	 * Lookup the named node using an XPath like syntax
	 * <ul>
	 * <li>node - searched for node under current node
	 * <li>//node - searches for first occurrence of node
	 * <li>/node1/node2 - returns node2 in tree
	 * <li>/node1[2]/node2 - returns node2 under 2nd repeated node1 element
	 * <li>/node1/node2[2] - returns second repeated node 2 element under node1
	 * </ul>
	 * 
	 * @param name
	 *            - path to node
	 * 
	 * @returns node or null if non found
	 */
	public HashList getNode(String name) {
		HashList node = this;
		int pos = 1; // default to first node in list

		// extract index if any
		if (name.endsWith("]")) {
			int i = name.indexOf('[');
			String s = name.substring(i + 1, name.length() - 1);
			name = name.substring(0, i);
			pos = Integer.parseInt(s);
		}

		if (name.startsWith("//")) {
			name = name.substring(2);
			return searchNode(name, pos);
		} else if ('/' == name.charAt(0)) {
			String[] nodes = name.split("/");

			node = getNode(nodes[1]);
			int i = 2;
			for (; i < (nodes.length - 1) && node != null; i++) {
				node = node.getNode(nodes[i]);
			}// for
			if (node != null) {
				return node.getNode(nodes[i], pos);
			} else {
				return null;
			}
		}

		if (node != null) {
			return node.getNode(name, pos);
		} else {
			return null;
		}
	}

	/**
	 * recurse tree looking for name
	 * 
	 * @param name
	 * @return
	 */
	private HashList searchNode(String name, int pos) {
		HashList node = null;

		if ((node = getNode(name, pos)) == null && children != null) {
			for (HashList child : children) {
				node = child.searchNode(name, pos);
				if (node != null) {
					return node;
				}

			}
		}

		return node;
	}

	/**
	 * Gets a named element at a specific position
	 * 
	 * @param name
	 *            element name
	 * @param pos
	 *            position
	 * @return HashList or null if not found
	 */
	public HashList getNode(String name, int pos) {
		if (index != null && pos > 0) {
			ArrayList<HashList> elements = index.get(name);
			if (elements != null) {
				if (pos > elements.size()) {
					return null;
				} else {
					return elements.get(pos - 1);
				}
			}
		}

		return null;
	}

	public Iterator<HashList> getChildren(String name) {
		if (index == null) {
			return null;
		}
		ArrayList<HashList> elements = index.get(name);
		return elements.iterator();
	}

	public boolean add(HashList arg0) {

		if (children == null) {
			return false;
		}
		return children.add(arg0);
	}

	public boolean addAll(Collection<? extends HashList> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	public void clear() {
		children.clear();

	}

	public boolean contains(Object arg0) {
		return children.contains(arg0);
	}

	public boolean containsAll(Collection<?> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean isEmpty() {
		if (children == null) {
			return true;
		}

		return children.isEmpty();
	}

	public Iterator<HashList> iterator() {
		if (children != null) {
			return children.iterator();
		} else {
			return null;
		}
	}

	public boolean remove(Object arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean removeAll(Collection<?> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean retainAll(Collection<?> arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	public int size() {
		if (children != null) {
			return children.size();
		}
		return 0;
	}

	public Object[] toArray() {
		// TODO Auto-generated method stub
		return null;
	}

	public <T> T[] toArray(T[] arg0) {
		// TODO Auto-generated method stub
		return null;
	}
}